﻿using System;
using System.Collections.Generic;
using System.Linq;
using DependencyAnalysis;
using DependencyAnalysis.DataModel;
using LiskovSubstitutionLibrary;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using PrinciplesP = DependencyAnalysis.Principles;

namespace UnitTests.Principles
{
    [TestClass]
    public class LiskovSubstitutionPrincipleTest
    {
        private static readonly IDependencyModel Model;

        private TypeDescriptor _childClassType;
        private TypeDescriptor _baseClassType;
        private static readonly IDictionary<TypeDescriptor, IEnumerable<MemberDescriptor>> LspResult;

        static LiskovSubstitutionPrincipleTest()
        {
            var setup = Setup.BuildStandardSetup();
            Model = setup.Model;
            setup.StartAnalysis(new[] { typeof(BaseClass).Assembly });
            LspResult = PrinciplesP.LiskovSubstitution(Model);
        }

        [TestInitialize]
        public void Initialize()
        {
            _childClassType = (TypeDescriptor)Model.DependenciesPerTypeOut.Keys.First(
                x => Equals(x, DescriptorBuilder.BuildTypeDescriptor(typeof(ChildClass))));
            _baseClassType = (TypeDescriptor)Model.DependenciesPerTypeOut.Keys.First(
                x => Equals(x, DescriptorBuilder.BuildTypeDescriptor(typeof(BaseClass))));
        }


        [TestMethod]
        public void WhenModelIsNullThrowArgumentNullException()
        {
            Assert1.Throws<ArgumentNullException>(() => PrinciplesP.LiskovSubstitution(null));
        }

        [TestMethod]
        public void WhenNoViolationsFoundThenReturnEmptyEnumeration()
        {
            var result = PrinciplesP.LiskovSubstitution(new DependencyModel());
            Assert.AreEqual(0, result.Count());
        }

        private static void VerifyMemberReported(TypeDescriptor reportedType, string memberName, bool mustReport)
        {
            var memberDescriptor = reportedType.Members.First(x => x.Name == memberName);
            Assert.AreEqual(mustReport, LspResult[reportedType].Contains(memberDescriptor));
        }

        [TestMethod]
        public void WhenMethodHidesBaseMemberThenReport()
        {
            VerifyMemberReported(_childClassType, "HiddenMethod", true);
        }

        [TestMethod]
        public void WhenMemberIsNoMethodThenIgnore()
        {
            VerifyMemberReported(_childClassType, "HiddenField", false);
        }

        [TestMethod]
        public void WhenMethodIsNoOverrideThenIgnore()
        {
            VerifyMemberReported(_childClassType, "NoOverride", false);
        }

        [TestMethod]
        public void WhenMemberIsStaticThenIgnore()
        {
            VerifyMemberReported(_childClassType, "Foo", false);
        }

        [TestMethod]
        public void WhenMemberDirectlyImplementsAbstractThenIgnore()
        {
            VerifyMemberReported(_childClassType, "AbstractMethod", false);
        }

        [TestMethod]
        public void WhenMemberDirectlyImplementsInterfaceThenIgnore()
        {
            VerifyMemberReported(_childClassType, "DirectInterfaceMethod", false);
        }

        [TestMethod]
        public void WhenViolatingMethodOverridesInterfaceMethodThenReport()
        {
            VerifyMemberReported(_childClassType, "BaseInterfaceMethod", true);
        }

        [TestMethod]
        public void WhenOverrideDoesNotCallBaseThenReport()
        {
            VerifyMemberReported(_childClassType, "NoBaseCall", true);
        }

        [TestMethod]
        public void WhenOverrideIsCorrectThenIgnore()
        {
            VerifyMemberReported(_childClassType, "GoodOverride", false);
        }

        [TestMethod]
        public void WhenMethodReturnsBeforeCallingBaseThenReport()
        {
            VerifyMemberReported(_childClassType, "ReturnBeforeBaseCall", true);
        }

        [TestMethod]
        public void WhenMethodThrowsBeforeCallingBaseThenReport()
        {
            VerifyMemberReported(_childClassType, "ThrowBeforeBaseCall", true);
        }

        [TestMethod]
        public void WhenMethodThrowsNewExceptionTypeThenReport()
        {
            VerifyMemberReported(_childClassType, "ThrowNewExceptionType", true);           
        }

        [TestMethod]
        public void WhenMethodThrowsBaseExceptionTypeThenIgnore()
        {
            VerifyMemberReported(_childClassType, "ThrowBaseExceptionType", false);
        }

        [TestMethod]
        public void WhenMethodThrowsNewExceptionTypeInCalledMethodThenReport()
        {
            VerifyMemberReported(_childClassType, "ThrowNewExceptionTypeInCall", true);
        }

        [TestMethod]
        public void WhenMethodThrowsBaseExceptionBaseCalledAnotherMethodThenIgnore()
        {
            VerifyMemberReported(_childClassType, "ThrowBaseExceptionTypeInCall", false);
        }

        [TestMethod]
        public void WhenMethodModifiesNewFieldsThenIgnore()
        {
            VerifyMemberReported(_childClassType, "ChangeOwnState", false);
        }

        [TestMethod]
        public void WhenMethodModifiesBaseFieldsThenReport()
        {
            VerifyMemberReported(_childClassType, "ChangeBaseState", true);
        }

        [TestMethod]
        public void WhenReturnValueIsModifiedAfterBaseCallThenReport()
        {
            VerifyMemberReported(_childClassType, "ReturnOtherValue", true);
        }

        [TestMethod]
        public void WhenLocalIsCopiedAndReturnedThenIgnore()
        {
            VerifyMemberReported(_childClassType, "CopyLocal", false);
        }

        [TestMethod]
        public void WhenReturnValueIsStoredLocalAndOtherLocalReturnedThenReport()
        {
            VerifyMemberReported(_childClassType, "CopyLocalReturnWrong", true);
        }

        [TestMethod]
        public void WhenReturnOtherObjectThanBaseThenReport()
        {
            VerifyMemberReported(_childClassType, "ReturnOtherObject", true);
        }

        [TestMethod]
        public void WhenReturnSameObjectLikeBaseThenReport()
        {
            VerifyMemberReported(_childClassType, "ReturnSameObject", false);
        }

        [TestMethod]
        public void WhenMethodCallsStateChangingMethodsOfBaseThenReport()
        {
            VerifyMemberReported(_childClassType, "CalledBaseMethodModifiesBaseField", true);
        }

        [TestMethod]
        public void WhenMethodCallsStateChangingMethodsOfSelfThenReport()
        {
            VerifyMemberReported(_childClassType, "CalledOwnMethodModifiesBaseField", true);
        }

        [TestMethod]
        public void WhenOverridingMethodOfSystemObjectThenIgnore()
        {
            VerifyMemberReported(_baseClassType, "ToString", false);
        }
    }
}
